
#include <assert.h>
#include <fcntl.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <vector>

#include "Utils.h"
#include "AppLogger.h"
#include "HttpHelper.h"
#include "Hop.h"
#include "ProxyServer.h"

using namespace std;

static const char *optString = "+n:s:d:p:h?";

static AppLogger * logger = AppLogger::getInstance("Proxy");

struct ProxServerConfig proxServerConfig; // the program configuration

static ProxyServer * server;

// print the program configuration
static void printProgConfig(ProxServerConfig config);

//parsing the command line options
static int parseCommandLine(int argc, char *argv[]);

/*
 *The SIG_TERM handler for the main process.
 */
static void sigTermHandler(int sigNo) {
	server->gracefulShutDown();
}

// main function
int main(int argc, char ** argv) {

	// parse the command line options
	parseCommandLine(argc, argv);

	printProgConfig(proxServerConfig);

	Utils::daemonize();

    //set up the SIGTERM handler
    Utils::setupSigHandler(SIGTERM, sigTermHandler);

    // ignore SIG_PIPE so that we don't crash when writing to a broken pipe
    Utils::setupSigHandler(SIGPIPE, SIG_IGN);

	// start the proxy server with the given configuration
	server = new ProxyServer(proxServerConfig);

	server->run();

	return 0;
}

/*
 * Print the usage information to the user
 */
void printUsage(void) {
	cout
			<< "======\n"
			<< "usage:\n"
			<< "======\n"
			<< "	nwprogproxy [options] "
			<< endl
			<< endl
			<< "Options:\n"
			<< "    -p port " << endl
			<< "		Specify the port the proxy should listen to"
			<< endl
			<< endl
			<< "    -d directory " << endl
			<< "		Specify the cache directory"
			<< endl
			<< endl
			<< "    -s domain " << endl
			<< "		Specify the domain to do reverse proxying"
			<< endl
			<< endl
			<< "    -n next-hop-servers " << endl
			<< "		Specify the next hop web servers used in reverse proxying"
			<< endl
			<< endl;

}

bool validConfig(ProxServerConfig config) {
	if (config.cacheDir.empty()) {
		return false;
	}

	if (config.proxyPort <=0) {
		return false;
	}

	if (config.servingDomain.empty()) {
		return false;
	}

	if (config.nextHopServers.empty()) {
		return false;
	}

	return true;
}


int parseCommandLine(int argc, char *argv[]) {

	// check the argument list

	// if argument list is empty, print the usage.
	if (1 == argc) {
		printUsage();

		exit(EXIT_FAILURE);
	}

	/* Process the arguments with getopt(), then
	 * populate streamerConfig.
	 */
	int opt = getopt(argc, argv, optString);
	while (opt != -1) {

		switch (opt) {
		case 'p': // the port this server is going to listen to
			if (NULL != optarg) {
				proxServerConfig.proxyPort = atoi(optarg);
			} else {
				printUsage();
				exit(EXIT_FAILURE);
			}
			break;

		case 'd': // cache directory
			if (NULL != optarg) {
				proxServerConfig.cacheDir = string(optarg);
			} else {
				printUsage();
				exit(EXIT_FAILURE);
			}
			break;

		case 's': // domain for reverse proxy
			if (NULL != optarg) {
				proxServerConfig.servingDomain = string(optarg);
			} else {
				printUsage();
				exit(EXIT_FAILURE);
			}
			break;

		case 'n': // next hop servers
			if (NULL != optarg) {
				string nextHopsInfo = string(optarg);
				Utils::parseHops(nextHopsInfo, proxServerConfig.nextHopServers);
				break;
			}

			cerr << "invalid next-hop servers info" << endl;
			printUsage();
			exit(EXIT_FAILURE);

		case 'h':
		case '?':
			printUsage();
			exit(EXIT_FAILURE);
			break;

		default:
			printUsage();

			exit(EXIT_FAILURE);
		}	// end of case

		opt = getopt(argc, argv, optString);
	}

	if (!validConfig(proxServerConfig)) {
		printUsage();

		exit(EXIT_FAILURE);
	}
	return 0;
}

void printProgConfig(ProxServerConfig config) {
	cerr << "Program configurations: " << endl;

	cerr << "	proxyPort = " << config.proxyPort << endl;
	cerr << "	cacheDir = " << config.cacheDir << endl;

	if (!config.servingDomain.empty()) {
		cerr << "	reverse-proxy-domain = " << config.servingDomain << endl;
	}

	cerr << " nextHop servers:" << endl;
	for (unsigned int i = 0; i < config.nextHopServers.size(); i++) {
		Hop * hop = config.nextHopServers.at(i);
		cerr << hop->host << ":" << hop->port << endl;
	}

}
